//
// Created by 袁绪龙 on 2024/4/25.
//

#include "OpenGLWidget2.h"
#include "qdebug.h"

/**
 * 在OpenGL中，VAO，VBO和EBO都是特殊的缓冲区类型，用来有效地在CPU和GPU之间传输数据。它们之间的区别和关系可以通过下面的方式来理解：
 *
    VBO（Vertex Buffer Object）: 顶点缓冲对象，用于存储顶点数据，如顶点的位置、颜色、法线、纹理坐标等。它可以高效地将这些数据发送到GPU中，然后在顶点着色器中进行处理。
    EBO（Element Buffer Object）或IBO（Index Buffer Object）: 元素缓冲对象，它主要存储顶点索引数据。由于很多图形在渲染的时候都有相同的顶点，EBO可以让我们直接复用这些顶点，从而节省存储空间，提高效率。
    VAO（Vertex Array Object）: 顶点数组对象，用于保存VBO的状态信息，即保存与一个VBO相关联的所有状态，包括绑定的VBO和属性指针信息等。当需要多个VBO时，VAO可以帮助我们在不同的VBO之间快速切换，提高渲染效率。

    在OpenGL中，VAO、VBO及EBO都是要配合使用的。一般情况下，我们通过VBO存储顶点数据，通过EBO存储索引数据，然后将这些状态通过VAO进行保存。当需要渲染图形的时候，
    我们可以直接绑定对应的VAO，就能快速地进行渲染。这样大大提高了渲染效率，减少了CPU和GPU之间的数据传输。
 */

namespace opengl2{
// 创建VBO和VAO对象
    unsigned int VBO, VAO, EBO;
/// 链接着色器的对象
    unsigned int shaderProgram;

/// 顶点着色器的位置数据
    float vertices[] = {
            0.5F, 0.5F, 0.0F, // top right
//            0.5F, -0.5F, 0.0F, // bottom right
//            -0.5F, 0.5F, 0.0F, // top left

            0.5F, -0.5F, 0.0F, // bottom right
            -0.5F, -0.5F, 0.0F, // bottom left
            -0.5F, 0.5F, 0.0F, // top left
    };

    /// EBO的数据 - 需要使用vertices的哪个点位
    unsigned int indices[] = {
            0, 1, 3,
            1, 2, 3
    };

// 顶点着色器
    const char *vertexShaderSource = "#version 330 core\n"
                                     "layout (location = 0) in vec3 aPos;\n" "void main()\n"
                                     "{\n"
                                     " gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
                                     "}\0";

// 片元着色器
    const char *fragmentShaderSource = "#version 330 core\n"
                                       "out vec4 FragColor;\n"
                                       "void main()\n"
                                       "{\n"
                                       " FragColor = vec4(1.0f, 0.5f, 0.2f, 10.f);\n" "}\n\0";

}


OpenGLWidget2::OpenGLWidget2(QWidget *parent) {}

void OpenGLWidget2::initializeGL() {
    qDebug() << "initializeGL";
    // 初始化opengl - 不初始化就使用不了opengl
    initializeOpenGLFunctions();

    // VBO和VAO对象赋予ID
    glGenVertexArrays(1, &opengl2::VAO);
    glGenBuffers(1, &opengl2::VBO);

    // 绑定VBO和VAO对象
    glBindVertexArray(opengl2::VAO);
    glBindBuffer(GL_ARRAY_BUFFER, opengl2::VBO);

    // 为当前绑定到target的缓冲区对象创建一个新的数据存储
    // 如果data不是NULL，则使用来自此指针的数据初始化数据存储
    glBufferData(GL_ARRAY_BUFFER, sizeof(opengl2::vertices), opengl2::vertices, GL_STATIC_DRAW);

    /// 告知显卡如何解析缓冲里的属性值 -> 用途是为顶点设定属性指针。
    //0：这是指的顶点属性的位置。在着色器中你需要用 layout(location = 0) 来设置它的位置。
    //3：指定顶点属性的大小。你的顶点属性是一个vec3，所以它包含3个值。因此这里的大小是3。
    //GL_FLOAT：指定数据的类型，这里是GL_FLOAT，也就是浮点型。
    //GL_FALSE：定义了我们是否希望数据被标准化。这里把它设置为GL_FALSE。
    //3 * sizeof(float)：这个是步长。由于每个顶点的属性连续排列，我们直接按照顶点属性的大小032B跨过它们就可以。
    //(void*) 0：这是位置数据在缓冲中起始的位置。因为我们从头开始，所以这里是0。
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float),  (void*) 0);
    // 开启VAO管理的第一个属性值
    glEnableVertexAttribArray(0);

    // 用完就清空下绑定
    glBindBuffer(GL_ARRAY_BUFFER, 0);
//    glBindVertexArray(0);

    // 加载顶点着色器
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &opengl2::vertexShaderSource, nullptr);
    glCompileShader(vertexShader);
    // 加载片元着色器
    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &opengl2::fragmentShaderSource, nullptr);
    glCompileShader(fragmentShader);
    // 链接着色器 - 用glLinkProgram链接，然后用glUserProgram使用
    opengl2::shaderProgram = glCreateProgram();
    glAttachShader(opengl2::shaderProgram, vertexShader);
    glAttachShader(opengl2::shaderProgram, fragmentShader);
    glLinkProgram(opengl2::shaderProgram);
    // 链接上之后把两个着色器删除
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    // 改变图形的渲染模式 - 用线条填充的方式
    // 第一个参数决定了函数操作的面。GL_FRONT_AND_BACK表示设置的模式将同时应用于物体的前向面和后向面。你也可以只改变前向面的模式（GL_FRONT）或后向面的模式（GL_BACK）。
    // 第二个参数决定了绘制模式。GL_LINE表示以线框模式绘制物体，也就是你只会看到物体的边缘线条，而不会看到物体表面的填充。
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

    // EBO的初始化 - 绑定小助理、然后带上数据 - 跟VAO差不多一样的初始化逻辑
    glGenBuffers(1, &opengl2::EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, opengl2::EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(opengl2::indices), opengl2::indices, GL_STATIC_DRAW);

    // 解绑VAO、EBO
//    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

}

void OpenGLWidget2::resizeGL(int w, int h) {
}

void OpenGLWidget2::paintGL() {
    qDebug() << "paintGL";
    glClearColor(0.2F, 0.3F, 0.3F, 1.0F);
    glClear(GL_COLOR_BUFFER_BIT);

    glUseProgram(opengl2::shaderProgram);
    glBindVertexArray(opengl2::VAO);
//    glDrawArrays(GL_TRIANGLES, 0, 6); // 从0开始找6个点 - count代表几个点位 vertices几组数据

    // 使用EBO进行绘制
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
}
